#ifndef GMAP_DYNARRAY_
#define GMAP_DYNARRAY_

#include <cstring>

#include <memory>
#include <type_traits>
#include <utility>

#include "range"

template <typename T, size_t Dims>
class dynarray;

template <typename T, unsigned Sub, size_t Dims>
class dynarray_info {
    friend class dynarray_info<T, Sub - 1, Dims>;

private:
    const size_t *subsizes_;
    const size_t *suboffs_;

protected:
    inline
    explicit dynarray_info(const size_t *sizes, const size_t *offs) :
        subsizes_(sizes),
        suboffs_(offs)
    {
    }

    inline
    explicit dynarray_info(const dynarray_info<T, Sub + 1, Dims> &parent) :
        subsizes_(parent.subsizes_ + 1),
        suboffs_(parent.suboffs_ + 1)
    {
    }

public:
    static const size_t dims = Dims;

    inline
    size_t get_size(unsigned dim) const
    {
        return subsizes_[dim];
    }

    inline
    size_t get_offset(unsigned dim) const
    {
        return suboffs_[dim];
    }

    template <unsigned Dim>
    inline
    size_t get_total_offset(size_t idx) const
    {
        // For the last level we just return the given index
        return idx;
    }

    template <unsigned Dim, typename... Idx>
    inline
    size_t get_total_offset(size_t idx, Idx... idxs) const
    {
        return idx * suboffs_[Dim] +
               get_total_offset<Dim + 1>(idxs...);
    }

    template <unsigned Dim = Dims - Sub>
    size_t get_total_size() const
    {
        size_t ret = 1;
        for (unsigned d = Dims - Sub; d < Dims; ++d) {
            ret *= subsizes_[d];
        }

        return ret;
    }

    inline
    const size_t *range() const
    {
        return subsizes_;
    }
};

template <typename T, unsigned Sub, size_t Dims>
class subarray;

template <typename T, unsigned Sub, size_t Dims>
class const_subarray;

template <typename T, unsigned Sub, size_t Dims>
class subarray :
    public dynarray_info<T, Sub, Dims> {
    using parent_info = dynarray_info<T, Sub, Dims>;

    friend class subarray<T, Sub - 1, Dims>;

protected:
    T *subdata_;

    inline
    subarray(T *data, const size_t *sizes, const size_t *offs) :
        dynarray_info<T, Sub, Dims>(sizes, offs),
        subdata_(data)
    {
    }

    inline
    subarray(subarray<T, Sub + 1, Dims> &parent, T *data) :
        dynarray_info<T, Sub, Dims>(parent),
        subdata_(data)
    {
    }

public:
    inline
    subarray<T, Sub - 1, Dims> operator[](unsigned idx)
    {
        size_t off = parent_info::get_offset(0);
        return subarray<T, Sub - 1, Dims>(*this, &subdata_[idx * off]);
    }

    inline
    const_subarray<T, Sub - 1, Dims> operator[](unsigned idx) const
    {
        size_t off = parent_info::get_offset(0);
        return const_subarray<T, Sub - 1, Dims>(*this, &subdata_[idx * off]);
    }

    template <typename... Idx>
    inline
    T &operator()(Idx... idxs)
    {
        static_assert(Dims == sizeof...(idxs), "Number of dimensions do not match");

        return subdata_[parent_info::template get_total_offset<0>(idxs...)];
    }

    template <typename... Idx>
    inline
    const T &operator()(Idx... idxs) const
    {
        static_assert(Dims == sizeof...(idxs), "Number of dimensions do not match");

        return subdata_[parent_info::template get_total_offset<0>(idxs...)];
    }

    bool operator==(const subarray &s) const
    {
        if (this != &s) {
            return std::equal(subdata_, subdata_ + parent_info::template get_total_size(),
                              s.subdata_);
        } else {
            return true;
        }
    }
};

template <typename T, unsigned Sub, size_t Dims>
class const_subarray :
    public dynarray_info<T, Sub, Dims> {
    const T *subdata_;

    using parent_info = dynarray_info<T, Sub, Dims>;

    friend class subarray<T, Sub - 1, Dims>;
    friend class const_subarray<T, Sub - 1, Dims>;

protected:
    inline
    const_subarray(const subarray<T, Sub + 1, Dims> &parent, const T *data) :
        dynarray_info<T, Sub, Dims>(parent),
        subdata_(data)
    {
    }

    inline
    const_subarray(const_subarray<T, Sub + 1, Dims> &parent, const T *data) :
        dynarray_info<T, Sub, Dims>(parent),
        subdata_(data)
    {
    }

public:
    static const size_t dims = Dims;
    
    inline
    const_subarray<T, Sub - 1, Dims> operator[](unsigned idx) const
    {
        size_t off = parent_info::get_offset(0);
        return const_subarray<T, Sub - 1, Dims>(*this, &subdata_[idx * off]);
    }

    template <typename... Idx>
    inline
    const T &operator()(Idx... idxs) const
    {
        static_assert(Dims == sizeof...(idxs), "Number of dimensions do not match");

        return subdata_[parent_info::template get_total_offset<0>(idxs...)];
    }

    bool operator==(const const_subarray &s) const
    {
        if (this != &s) {
            return std::equal(subdata_, subdata_ + parent_info::template get_total_size(),
                              s.subdata_);
        } else {
            return true;
        }
    }

    bool operator==(const subarray<T, Sub, Dims> &s) const
    {
        if (this != &s) {
            return std::equal(subdata_, subdata_ + parent_info::template get_total_size(),
                              s.subdata_);
        } else {
            return true;
        }
    }
};



template <typename T, size_t Dims>
class subarray<T, 1, Dims> :
    public dynarray_info<T, 1, Dims> {
    friend class subarray<T, 2, Dims>;

protected:
    T *subdata_;

    inline
    subarray(subarray<T, 2, Dims> &parent, T *data) :
        dynarray_info<T, 1, Dims>(parent),
        subdata_(data)
    {
    }

public:
    inline
    T &operator[](unsigned idx)
    {
        return subdata_[idx];
    }

    inline
    const T &operator[](unsigned idx) const
    {
        return subdata_[idx];
    }

    inline
    T &operator()(unsigned idx)
    {
        return subdata_[idx];
    }

    inline
    const T &operator()(unsigned idx) const
    {
        return subdata_[idx];
    }
};

template <typename T, size_t Dims>
class const_subarray<T, 1, Dims> :
    public dynarray_info<T, 1, Dims> {
    const T *subdata_;

    friend class subarray<T, 2, Dims>;
    friend class const_subarray<T, 2, Dims>;

protected:
    inline
    const_subarray(const subarray<T, 2, Dims> &parent, const T *data) :
        dynarray_info<T, 1, Dims>(parent),
        subdata_(data)
    {
    }

    inline
    const_subarray(const_subarray<T, 2, Dims> &parent, const T *data) :
        dynarray_info<T, 1, Dims>(parent),
        subdata_(data)
    {
    }

public:
    inline
    const T &operator[](unsigned idx) const
    {
        return subdata_[idx];
    }

    inline
    const T &operator()(unsigned idx) const
    {
        return subdata_[idx];
    }
};


template <typename T, size_t Dims>
class dynarray : 
    public subarray<T, Dims, Dims> {

    using parent_subarray = subarray<T, Dims, Dims>;

    std::unique_ptr<T[]> data_;

    size_t sizes_[Dims];
    size_t offs_[Dims - 1];

public:
    template <typename... DimSizes>
    dynarray(DimSizes ...sizes) :
        parent_subarray(new T[init_dynarray(0, sizes...)], sizes_, offs_),
        data_(parent_subarray::subdata_)
    {
        static_assert(Dims > 0, "Number of dimensions must be greater than 0");
        static_assert(Dims == sizeof...(sizes), "Number of dimensions do not match");
    }

private:
    size_t init_dynarray(unsigned index, size_t dim_size)
    {
        sizes_[index] = dim_size;

        // Once all dimensions are in place, compute the offsets for each dimension
        long unsigned next_off = 1;
        for (unsigned i = Dims - 1; i > 0; --i) {
            next_off *= sizes_[i];
            offs_[i - 1] = next_off;
        }

        return dim_size;
    }

    template <typename... DimSizes>
    size_t init_dynarray(unsigned index, size_t dim_size, DimSizes ...sizes)
    {
        sizes_[index] = dim_size;

        return dim_size * init_dynarray(index + 1, sizes...);
    }
};

#endif

/* vim:set ft=cpp backspace=2 tabstop=4 shiftwidth=4 textwidth=120 foldmethod=marker expandtab: */
